package system;

import org.apache.log4j.Logger;
import utils.Buffer;
import utils.Consts;

import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 磁盘对象
 *
 * @author waxijiang
 */
public class Disk implements Serializable {
    /**
     * 日志
     */
    static Logger logger = Logger.getLogger(Disk.class);

    /**
     * 磁盘最大容量为1MB
     * 磁盘由 1000kb的内容区块(2000块) + 0.5k的超级块(1块) + 0.5的根目录块(1块) + 2.5k位图块(5块) +50k的inode块构成(100块)
     */
    public static Double MAX_VOLUME = 1053.5 * Consts.KB;

    /**
     * 磁盘最大块数
     */
    public static final int MAX_BLOCK_NUM = (int) (Disk.MAX_VOLUME / SuperBlock.FILE_BLOCK_MAX_VOLUME);

    /**
     * 文件块
     */
    private final FileBlock[] fileBlocks;
    private final SuperBlock superBlock;
    private final Bitmap bitmap;
    private final InodeBlock inodeBlock;
    private FileItem root;


    public Disk() {
        logger.info("磁盘初始化");
        superBlock = new SuperBlock();
        bitmap = new Bitmap();
        inodeBlock = new InodeBlock(superBlock.getINODE_COUNT());

        fileBlocks = new FileBlock[MAX_BLOCK_NUM];
        // 创建磁盘时分配块
        for (int i = 0; i < MAX_BLOCK_NUM; i++) {
            fileBlocks[i] = new FileBlock();
        }
    }

    /**
     * 从磁盘加载
     *
     * @return Disk对象
     */
    public static Disk load() {
        Disk disk = null;
        try {
            FileInputStream fileInputStream = new FileInputStream(SuperBlock.DISK_FILE_LOCATION);
            ObjectInputStream in = new ObjectInputStream(fileInputStream);
            disk = (Disk) in.readObject();
            logger.info("扫描到磁盘, 装载.");
            return disk;
        } catch (IOException e) {
            if (e instanceof FileNotFoundException) {
                logger.info("未扫描到存在磁盘文件,新建磁盘");
                disk = new Disk();
                // 将系统块的使用状况置为1
                for (int i = 0; i < disk.superBlock.getSYSTEM_BLOCK(); i++) {
                    disk.bitmap.use(i);
                }

                logger.info("挂载根目录");
                disk.root = new FileItem("/", Consts.DIR, Consts.FILE_ITEM_STD_SIZE, Consts.READ_AND_WRITE,
                        Consts.ONLY_READ, new Date(), new Date(),
                        "root", disk.superBlock.getSYSTEM_BLOCK(), disk.inodeBlock);
                // 标记使用
                disk.bitmap.use(disk.superBlock.getSYSTEM_BLOCK());
                if (disk.mountFileItem(disk.root, disk.ln(disk.root, "."), disk.root.getSize())) {
                    logger.info("挂载成功");
                }
                // 创建root目录
                logger.info("挂载root目录到/上 ");
                int availableBlockIndex = disk.bitmap.getAvailableBlockIndex();
                FileItem userDir = new FileItem("root", Consts.DIR, Consts.FILE_ITEM_STD_SIZE, Consts.READ_AND_WRITE,
                        Consts.ONLY_READ, new Date(), new Date(),
                        "root", availableBlockIndex, disk.inodeBlock);
                disk.bitmap.use(availableBlockIndex);

                if (disk.mountFileItem(disk.root, userDir, userDir.getSize())) {
                    logger.info("挂载成功");
                }

                logger.info("挂载root自己到自己上");
                if (disk.mountFileItem(userDir, disk.ln(userDir, Consts.CURRENT_DIR), userDir.getSize())) {
                    logger.info("挂载成功");
                }

                logger.info("挂载/作为root的父目录");
                if (disk.mountFileItem(userDir, disk.ln(disk.root, Consts.PARENT_DIR), disk.root.getSize())) {
                    logger.info("挂载成功");
                }
                return disk;
            } else {
                return null;
            }
        } catch (ClassNotFoundException e) {
            logger.error(e.getStackTrace());
            return null;
        }
    }

    public FileItem getRoot() {
        return root;
    }

    public void releaseBlock(int index){
        fileBlocks[index].content = null;
        this.bitmap.release(index);
    }

    public boolean mountFileItem(FileItem targetDir, FileItem mountedFileItem, int mountedFileItemSize) {
        //获取挂载的目录的最后一个文件块
        int lastBlockIndex = targetDir.getInode().getLastBlockIndex();
        Disk.FileBlock fileBlock = this.getFileBlock(lastBlockIndex);
        // 获取文件块内容
        Object content = fileBlock.getContent();
        int size = fileBlock.getSize();
        // 如果一个文件块可以完全存储
        if (size + mountedFileItemSize < Disk.SuperBlock.FILE_BLOCK_MAX_VOLUME) {
            // 因为要挂载的肯定是目录，目录理应就是List结构
            if (content instanceof List) {
                List<FileItem> fileItem = (List<FileItem>) content;
                fileItem.add(mountedFileItem);
                fileBlock.setContent(fileItem, size + mountedFileItemSize);
                return true;
                // 如果是空目录
            } else if (content == null) {
                List<FileItem> fileItems = new ArrayList<FileItem>();
                fileItems.add(mountedFileItem);
                fileBlock.setContent(fileItems, mountedFileItemSize);
                return true;
            } else {
                // 发生甚么事了？
                System.out.println("???");
                return false;
            }
            //如果一个文件块无法完成存储，寻找新文件块。
        } else if (mountedFileItemSize < Disk.SuperBlock.FILE_BLOCK_MAX_VOLUME) {
            int availableBlockIndex = this.bitmap.getAvailableBlockIndex();
            if (availableBlockIndex != -1) {
                targetDir.getInode().fileBlockIndex.add(availableBlockIndex);
                Disk.FileBlock fileBlock1 = this.getFileBlock(availableBlockIndex);
                List<FileItem> fileItem = new ArrayList<FileItem>();
                fileItem.add(mountedFileItem);
                fileBlock1.setContent(fileItem, mountedFileItemSize);
                return true;
            } else {
                Buffer.getInstance().write("disk has not enough file block to store!");
                return false;
            }
        } else {
            Buffer.getInstance().write("your file is too big to store!");
            return false;
        }
    }

    /**
     * 创建链接文件
     *
     * @param linkedFileItem 被链接的文件
     * @param filename       链接文件名
     * @return 新建的链接文件
     */
    public FileItem ln(FileItem linkedFileItem, String filename) {
        return this.createNewFileItem(filename, linkedFileItem.getInode());
    }

    /**
     * 使用inode来创建一个文件,该文件是inode对应文件的一个链接
     *
     * @param filename 文件名
     * @param inode    inode对象
     * @return 创建的文件对象, 如果空间不够则返回null
     */
    public FileItem createNewFileItem(String filename, Disk.Inode inode) {
        int availableBlockIndex = bitmap.getAvailableBlockIndex();
        if (availableBlockIndex != -1) {
            bitmap.use(availableBlockIndex);
            return new FileItem(filename, inode);
        } else {
            return null;
        }
    }

    public static void drop(Disk disk) {
        try {
            FileOutputStream fileOut =
                    new FileOutputStream(SuperBlock.DISK_FILE_LOCATION);
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(disk);
            out.close();
            fileOut.close();
            logger.info("disk data is saved in " + SuperBlock.DISK_FILE_LOCATION);
        } catch (IOException i) {
            logger.error(i.getStackTrace());
        }
    }

    public SuperBlock getSuperBlock() {
        return superBlock;
    }

    public Bitmap getBitmap() {
        return bitmap;
    }

    public InodeBlock getInodeBlock() {
        return inodeBlock;
    }

    public FileBlock getFileBlock(int index) {
        return fileBlocks[index];
    }

    /**
     * <b>位图表</b>
     * 用于记录文件块是否被使用
     *
     * @author waxijiang
     */
    public static class Bitmap implements Serializable {
        /**
         * 日志
         */
        static Logger logger = Logger.getLogger(Bitmap.class);

        private int[] isAvailable;

        public Bitmap() {
            logger.info("位图初始化");
            isAvailable = new int[MAX_BLOCK_NUM];
        }

        public int[] getIsAvailable() {
            return isAvailable;
        }

        /**
         * 设置使用块
         *
         * @param index 要使用的块索引
         */
        public void use(int index) {
            isAvailable[index] = 1;
        }

        /**
         * 释放块
         *
         * @param index 要释放的块
         */
        public void release(int index) {
            isAvailable[index] = 0;
        }

        /**
         * 获取空闲块索引
         *
         * @return 空闲块索引，如果无空闲块则为-1
         */
        public int getAvailableBlockIndex() {
            for (int i = 0; i < isAvailable.length; i++) {
                if (isAvailable[i] == 0) {
                    return i;
                }
            }
            return -1;
        }
    }

    /**
     * 文件块
     *
     * @author waxijiang
     */
    public static class FileBlock implements Serializable {

        /**
         * 日志
         */
        static Logger logger = Logger.getLogger(FileBlock.class);

        /**
         * 当前占用的字节数
         */
        private int size = 0;

        private Object content;

        /**
         * 默认构造函数
         */
        public FileBlock() {
        }

        public void setContent(Object content, int size) {
            if (size < MAX_VOLUME) {
                this.size = size;
                this.content = content;
            } else {
                logger.error("超过单个块最大长度。。");
            }
        }

        public Object getContent() {
            return content;
        }

        public int getSize() {
            return size;
        }

        public void setSize(int size) {
            this.size = size;
        }
    }

    /**
     * 索引节点，一个至少44字节
     *
     * @author waxijiang
     */
    public static class Inode implements Serializable {
        /**
         * 日志
         */
        static Logger logger = Logger.getLogger(Inode.class);

        /**
         * 用于辅助生成id
         */
        public static int generateId = 0;

        /**
         * inodeID
         */
        public int id;

        /**
         * 文件类型, file或者dir。占4个字节
         */
        public String fileType;
        /**
         * 文件大小，占4个字节
         */
        public int fileSize;

        /**
         * 用户权限，占1个字节
         */
        public int userPermission;

        /**
         * 其他人权限，占1个字节
         */
        public int otherPermission;

        /**
         * 创建时间，10个字节
         */
        public Date createTime;

        /**
         * 最后更新时间，10个字节
         */
        public Date updateTime;

        /**
         * 文件所属用户, 10个字节
         */
        public String own;

        /**
         * 文件实际存放的文件块地址，至少4个字节
         */
        public List<Integer> fileBlockIndex;

        public Inode() {
        }

        public Inode(String fileType, int fileSize, int userPermission, int otherPermission, Date createTime, Date updateTime, String own, List<Integer> fileBlockIndex) {
            logger.info("Inode初始化:");
            logger.debug("fileType:" + fileType + "\tfileSize:" + fileSize + "\tuserPermission:" + userPermission +
                    "\totherPermission:" + otherPermission + "\tcreateTime:" + createTime + "\tupdateTime:" + updateTime);
            this.fileType = fileType;
            this.fileSize = fileSize;
            this.userPermission = userPermission;
            this.otherPermission = otherPermission;
            this.createTime = createTime;
            this.updateTime = updateTime;
            this.own = own;
            this.fileBlockIndex = fileBlockIndex;
            this.id = generateId++;
        }

        public int getLastBlockIndex() {
            return this.fileBlockIndex.get(this.fileBlockIndex.size() - 1);
        }

        public boolean removeFileBlock(int index) {
            try {
                this.fileBlockIndex.remove(index);
                return true;
            } catch (Exception e) {
                logger.error(e.getStackTrace());
                return false;
            }
        }

        public void removeAllFileBlock() {
            this.fileBlockIndex = new ArrayList<Integer>();
        }

        public String getFileType() {
            return fileType;
        }

        public void setFileType(String fileType) {
            this.fileType = fileType;
        }

        public int getFileSize() {
            return fileSize;
        }

        public void setFileSize(int fileSize) {
            this.fileSize = fileSize;
        }

        public int getUserPermission() {
            return userPermission;
        }

        public void setUserPermission(int userPermission) {
            this.userPermission = userPermission;
        }

        public int getOtherPermission() {
            return otherPermission;
        }

        public void setOtherPermission(int otherPermission) {
            this.otherPermission = otherPermission;
        }

        public List<Integer> getFileBlockIndex() {
            return fileBlockIndex;
        }

        public void setFileBlockIndex(List<Integer> fileBlockIndex) {
            this.fileBlockIndex = fileBlockIndex;
        }

        public Date getCreateTime() {
            return createTime;
        }

        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }

        public Date getUpdateTime() {
            return updateTime;
        }

        public void setUpdateTime(Date updateTime) {
            this.updateTime = updateTime;
        }

        public String getOwn() {
            return own;
        }

        public void setOwn(String own) {
            this.own = own;
        }

        public int getId() {
            return id;
        }


    }

    /**
     * 索引节点块Inode
     *
     * @author waxijiang
     */
    public static class InodeBlock implements Serializable {
        /**
         * inode数组
         */
        private List<Inode> inodes;
        /**
         * 当前有效的inode个数
         */
        private int currentCount = 0;

        private int maxCount;

        /**
         * 构建<code>size</code>个inode
         *
         * @param count 要构建的inode块所占文件块的个数
         */
        public InodeBlock(int count) {
            maxCount = count;
            inodes = new ArrayList<Inode>();
        }

        public Inode getInode(String fileType, int fileSize, int userPermission, int otherPermission, Date createTime, Date updateTime, String own, List<Integer> fileBlockIndex) {
            Inode inode = new Inode(fileType, fileSize, userPermission, otherPermission, createTime, updateTime, own,
                    fileBlockIndex);

            if (currentCount + 1 <= maxCount) {
                inodes.add(inode);
                return inode;
            } else {
                return null;
            }
        }

        public boolean deleteInode(int id) {
            for (Inode inode : inodes) {
                if (inode.id == id) {
                    inodes.remove(inode);
                    return true;
                }
            }
            Buffer.getInstance().write("inode id not exist");
            return false;
        }

    }

    /**
     * 超级块，存储文件系统的关键参数
     *
     * @author waxijiang
     */
    public static class SuperBlock implements Serializable {

        /**
         * 用户文件路径
         */
        private final String USER_FILE_LOCATION = "src/main/resources/user.csv";

        /**
         * 欢迎信息
         */
        private final String WELCOME_MEG = "Welcome to HXX File System!\n";

        /**
         * 磁盘文件存储路劲
         */
        private static final String DISK_FILE_LOCATION = "src/main/resources/disk.dat";


        /**
         * 文件系统名字
         */
        private String SYSTEM_NAME = "HXX system.FileSystem";


        /**
         * 文件块最大字节数,默认512字节
         */
        public static int FILE_BLOCK_MAX_VOLUME = 512 * Consts.B;


        /**
         * 单个文件最大
         */
        private Integer SINGLE_FILE_MAX_SIZE = 1 * Consts.KB;

        /**
         * 超级块占用的块
         */
        private Integer SUPER_BLOCK_COUNT = 1;

        /**
         * 根目录占用的块的个数
         */
        private Integer ROOT_DIR_BLOCK_COUNT = 1;


        /**
         * 一个Inode块所占的大小, 默认是48字节, 因此一个块可以装10个inode, 表示10k.
         */
        private Integer SINGLE_INODE_SIZE =
                4 * (SINGLE_FILE_MAX_SIZE / FILE_BLOCK_MAX_VOLUME) + Consts.INODE_WITHOUT_INDEX_SIZE;

        /**
         * 单个块的inode个数
         */
        private Integer SINGLE_BLOCK_INODE_COUNT = FILE_BLOCK_MAX_VOLUME / SINGLE_INODE_SIZE;

        /**
         * bitmap块占用的块数, 默认是4块
         */
        private int BITMAP_BLOCK_COUNT = MAX_BLOCK_NUM / FILE_BLOCK_MAX_VOLUME;

        /**
         * Inode 块的总个数
         */
        private Integer INODE_BLOCK_COUNT = 100;

        /**
         * inode总数
         */
        private Integer INODE_COUNT = INODE_BLOCK_COUNT * SINGLE_BLOCK_INODE_COUNT;

        private Integer SYSTEM_BLOCK = SUPER_BLOCK_COUNT + BITMAP_BLOCK_COUNT + INODE_BLOCK_COUNT;

        public SuperBlock() {
        }

        public Integer getINODE_COUNT() {
            return INODE_COUNT;
        }

        public Integer getINODE_BLOCK_COUNT() {
            return INODE_BLOCK_COUNT;
        }

        public Integer getSYSTEM_BLOCK() {
            return SYSTEM_BLOCK;
        }

        public Integer getSINGLE_BLOCK_INODE_COUNT() {
            return SINGLE_BLOCK_INODE_COUNT;
        }

        /**
         * Inode总占的块数
         * <b>无法跨越的鸿沟<b/>
         * INode块由100个总共表示 1000kb
         */


        public String getUSER_FILE_LOCATION() {
            return USER_FILE_LOCATION;
        }

        public String getWELCOME_MEG() {
            return WELCOME_MEG;
        }

        public String getSYSTEM_NAME() {
            return SYSTEM_NAME;
        }

        public Integer getSINGLE_FILE_MAX_SIZE() {
            return SINGLE_FILE_MAX_SIZE;
        }

        public Integer getSUPER_BLOCK_COUNT() {
            return SUPER_BLOCK_COUNT;
        }

        public Integer getROOT_DIR_BLOCK_COUNT() {
            return ROOT_DIR_BLOCK_COUNT;
        }

        public Integer getSINGLE_INODE_SIZE() {
            return SINGLE_INODE_SIZE;
        }

        public int getBITMAP_BLOCK_COUNT() {
            return BITMAP_BLOCK_COUNT;
        }
    }
}
